Skip to content

Add --target for scanning an address without a target file - #37

Merged
bandrel merged 1 commit into
mainfrom
feat/target-flag
Aug 21, 2026
Merged

bandrel merged 1 commit into
mainfrom
feat/target-flag

Conversation

@bandrel

@bandrel bandrel commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Closes #4. Replaces #11's sibling PR #12, which I closed as unmergeable.

Why not just apply #12

#4 was filed in Jan 2021 and #12 tried to implement it two months later. It can't be applied: the source fork has been deleted and the code it patched has since been restructured (the config load it anchored to now lives in _load_config()).

Its approach also had three problems that matter more now than they did then:

  • It overwrote ranges.txt in place, without confirmation. That file is the engagement scope input and is tracked in-repo as a deliberately empty template.
  • The address was never validated.
  • The prompt sat at function-body indentation rather than inside the config branch, so it also fired in config-file mode — where target_file comes from JSON and ranges.txt is never read. It would prompt, accept an address, and silently have no effect.

What this does

./spoonmap.py --target 10.0.0.5
./spoonmap.py --target 10.0.0.0/24
./spoonmap.py --target 10.0.0.5,10.0.1.0/24,10.0.2.1-10.0.2.9

The value accepts anything a target file line accepts — bare IP, CIDR, A-B range, address netmask — comma-separated. It takes precedence over both config.json's target_file and the interactive prompt, so unlike a prompt-based approach it works in fully non-interactive runs.

Three properties worth calling out, each addressing one of the above:

ranges.txt is never touched. Addresses are written to cli_targets.txt under output_path via _atomic_write(). That keeps the operator's scope file intact and leaves a record on disk of what each run actually targeted. There's a test asserting a pre-existing ranges.txt is byte-for-byte unchanged.

Validation happens before anything else, and a bad entry exits non-zero naming the offending token. Silently accepting a typo would produce a clean run over an empty target set — a false negative with a report attached, which is this tool's worst outcome. --target --resume is also rejected rather than consuming the next flag as an address.

Validation reuses the target-file parser. _parse_range_line() is extracted from _parse_target_ranges() so the CLI cannot accept syntax the scan then fails to read. Two parsers would drift, and the drift would surface as a target accepted at the command line and silently not scanned. The extraction is behaviour-preserving — including the silent skip of unparseable lines (masscan accepts forms this parser doesn't model, and the same function runs on the exclusions file) and the IPv6 file/line warning; both have tests pinning them.

_resolve_cli_target() holds the argv-to-tokens glue rather than inlining it in main(), so the reject path is testable — main() carries a pre-existing # pragma: no cover as an interactive entry point.

Verification

  • 939 passed / 5 skipped / 100% coverage (gate is 95%), 32 new tests.
  • No suppressions added; the one pragma referenced above is pre-existing on main().
  • The refactor was confirmed behaviour-preserving by running the full suite before adding any new tests (907 passing, unchanged).

Round-trip is tested explicitly: what --target writes is parsed back by _parse_target_ranges() and asserted to produce the expected integer bounds.

🤖 Generated with Claude Code

Closes #4. Requested in Jan 2021 and attempted in PR #12, which could not be
applied: its fork is gone and the code it patched has since been restructured.
Its approach also overwrote ranges.txt in place, unvalidated, from a prompt that
fired even in config-file mode where the written file is never read.

--target accepts a comma-separated list of anything a target file line accepts
(bare IP, CIDR, A-B range, address netmask) and takes precedence over both
config.json's target_file and the interactive prompt, so it works in fully
non-interactive runs.

Three properties the earlier attempt lacked:

- ranges.txt is never touched. It is the operator's engagement scope input,
  tracked in-repo as an empty template. Addresses go to cli_targets.txt in
  output_path via _atomic_write(), which also leaves a record of what each run
  actually targeted.
- The value is validated before anything else happens, and a bad entry exits
  non-zero naming the token. Accepting a typo would produce a clean run over an
  empty target set — a false negative with a report attached.
- Validation reuses the target-file parser. _parse_range_line() is extracted
  from _parse_target_ranges() so the CLI cannot accept syntax the scan then
  fails to read; the extraction is behaviour-preserving, including the silent
  skip of unparseable lines and the IPv6 file/line warning.

_resolve_cli_target() holds the argv-to-tokens glue so the reject path is
tested: main() is an interactive entry point excluded from coverage.

939 tests pass (32 new), 100% coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bandrel
bandrel merged commit 13aef85 into main Aug 21, 2026
7 checks passed
@bandrel
bandrel deleted the feat/target-flag branch August 21, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

make it possible to give a single ip address/range withouth the need of a file

1 participant